%==========================================================================
%=  This file is part of the Sound Restoration Project
%=  (c) Copyright Industrial Mathematics Institute
%=                University of South Carolina, Department of Mathematics
%=  ALL RIGHTS RESERVED
%=
%=  Author: Borislav Karaivanov
%==========================================================================

%==========================================================================
% List of the files on which this procedure depends:
%
% readImageByIndex.m
% readImageFile.m
% readdpx.m
% read2DArrayFromFile.m
% setLogWritingFunctHandle.m
% write2DArrayToFile.m
% writeToLogFile.m
%
% image processing toolbox
%
%==========================================================================

%==========================================================================
% The function "extractSoundSignals" extracts a fixed number of sound
% signals from each frame in a specified index range. Each sound signal
% corresponds to a specified pair of vertical sound track bounds. Those
% signals are written to different text files - one file for each pair of
% sound track bounds.
% INPUT: "readFramesFunctHandle" is a handle to a function capable of
% reading any range of frames from a preset source.
% "frameRange" is a single integer or a row vector of two integers
% specifying the index of a single frame or a range of frames to be read
% from a preset source.
% "soundLength" is an integer specifying the length each of the extracted
% sound signals is expected to have.
% "soundBoundsArr" is a 2D array with two columns specifying the left and
% right vertical bounds of the sound track band in number of pixels. Each
% row corresponds to a pair of left and right bounds. It is expected that 
% 1 <= soundBoundsArr(k, 1) <= soundBoundsArr(k, 2) <= imageWidth for each
% valid value of k.
% "scratchFileNamePrefix" is a string specifying the common prefix in the
% names of all intermediate output files (including a full directory path).
% "waitbarStruct" is an optional parameter specifying a structure of
% parameters used to set a waitbar. Those parameters are a waitbar handle,
% a starting position on the waitbar (a number between 0 and 1), a relative
% length along the waitbar (a number between 0 and (1 - starting position),
% an initial value for a counter that counts the events whose progress is
% being illustrated by the waitbar, and a total reflecting the total number
% of events of the said kind.
% "writeToLogFunctHandle" is an optional function handle which writes to a
% fixed log text file if keeping a log is requested, or does nothing
% otherwise.
% OUTPUT: "outFileNameCellArr" returns a cell row vector of length equal
% to the number of given pairs of sound track bounds. Each cell holds the
% name of an output text file (including a full directory path) where all
% sound signals extracted from the frames are written. An empty cell
% indicates failure to write the corresponding text file.
% "allSignalsCellArr" returns a cell row vector of length equal to the
% number of given pairs of sound track bounds. Each cell holds a 2D array
% whose columns are the sound signals extracted from the frames in the
% specified range.
%==========================================================================
function [outFileNameCellArr, allSignalsCellArr] = ...
    extractSoundSignals(readFramesFunctHandle, frameRange, soundLength, ...
    soundBoundsArr, scratchFileNamePrefix, waitbarStruct, writeToLogFunctHandle)

if (nargin < 7)
    writeToLogFunctHandle = @(x) '';
end

% Specify that color frame are to be processed as grayscale images.
doConvertToGray = true;
% Initialize the output. Otherwise, in case of an early "return" (due to an
% interactive request to cancel) we will get a complaint that an output
% argument has not been assigned during the call to this function.
outFileNameCellArr = {''};

% Get the number of frames to be processed.
numFrames = frameRange(end) - frameRange(1) + 1;
% Get the number of pairs of sound bounds to be used.
numPairsOfBounds = size(soundBoundsArr, 1);

% Allocate memory for all short sound signals extracted from the frames.
allSignalsCellArr = cell(1, numPairsOfBounds);
allSignalsCellArr(:) = {zeros(soundLength, numFrames)};

% One by one, read in the frames, extract the sound track, convert it to
% sound signal, and write that sound signal to an ascii file.
frameInd = frameRange(1);
for ind = 1:numFrames
    
    % Read in the current frame.
    [frameArr, bitDepth] = readFramesFunctHandle(frameInd, doConvertToGray);

    % Assert that the sound signal from the current frame has the same
    % length as the sound signals from the previously read frames.
    assert((size(frameArr, 1) == soundLength), ...
        'SoundRestoration:extractSoundSignals:DifferentFrameHeight', ...
        'Height of frame %d differs from heights of the previous frames.', frameInd);
    
    % Compute the reciprocal of the maximal pixel value that a frame could
    % have.
    multiplier = 1/(2^bitDepth - 1);
    % Multiply all pixel values by the computed reciprocal.
    scaledImageArr = multiplier*double(frameArr);
    
    % Get the sound signal from the current scaled frame by averaging the
    % pixel values on each line of the frame between the left and right
    % bounds of the sound track.
    for k = 1:numPairsOfBounds
        allSignalsCellArr{k}(:, ind) = ...
            mean(scaledImageArr(:, soundBoundsArr(k, 1):soundBoundsArr(k, 2)), 2);
    end
    
    % Increment the frame index.
    frameInd = frameInd + 1;
    
    
    % Update the progress bar.
    if (exist('waitbarStruct', 'var') == 1)
        if getappdata(waitbarStruct.handle, 'canceling')
            return;
        end
        if isempty(waitbarStruct.total)
            waitbarStruct.total = numFrames;
        end
        waitbar(waitbarStruct.start + (waitbarStruct.length)*(ind/numFrames), ...
            waitbarStruct.handle, ...
            sprintf('Step %d of %d: extracting soundtrack %d out of %d', ...
            waitbarStruct.index, waitbarStruct.totalIndices, ...
            waitbarStruct.first + ind - 1, waitbarStruct.total));
    end
end

% Allocate memory for the names of the output files.
outFileNameCellArr = cell(1, numPairsOfBounds);

for k = 1:numPairsOfBounds
    % Define a name for the output text file.
    outFileNameCellArr{k} = [scratchFileNamePrefix ...
        'SndBounds' num2str(soundBoundsArr(k, 1)) '-' num2str(soundBoundsArr(k, 2)) ...
        '_Fr' num2str(frameRange(1)) '-' num2str(frameRange(end)) '.txt'];
    
    % Write the sound signals extracted from the frames to a text file.
    write2DArrayToFile(outFileNameCellArr{k}, allSignalsCellArr{k}, writeToLogFunctHandle);
end

% Write info to a log file.
writeToLogFunctHandle(['number of bits per pixel: ' num2str(bitDepth)]);

return;
% end of the function "extractSoundSignals"
